package com.as3.timbot.iso
{
	public class WorldMap
	{
	
	//===============================================
	//====mapAS Object===============================
	//This is an object that describes a grid.
	//It can be passed into the astar object to find a path between two tiles.
	public var rows:Number;
	public var columns:Number;
	public var terrain:Object;
	public var stairs:Array;
	public var map:Array;
	public var s:Object;//start position for the path, object in the map array
	public var g:Object;//goal position, object in the map array
	public var tileWidth:Number;
	public var tileHeight:Number;
	
	public function WorldMap(tempRows:Number, tempColumns:Number)
	{
		terrain = new Object();
		terrain.block = "wall";
		terrain.base = "grass";
		stairs = new Array();
		map = new Array();
		rows = tempRows;
		columns = tempColumns;
	}
	/*private function cloneProperties(whichMap:Map) {
		//This method will take the terrain properties and the dimensions from another map object
		terrain = whichMap.terrain.clone();///this is a problem, uses a shitty prototype...t.w.
		rows = whichMap.rows;
		columns = whichMap.columns;
	}*/
	public function clearSearchMarkers():void
	{
		for(var nx:Number = 0;nx<columns;nx++){
			for(var ny:Number = 0;ny<rows;ny++){
				map[nx][ny].exists = null;
			};
		}
	}
	public function clone():Object
	{
		var clone:Object = new Object();
		clone.rows=rows;
		clone.columns=columns;
		clone.terrain=terrain;
		clone.stairs=stairs;
		clone.map=map;
		clone.s=s;//start position for the path, object in the map array
		clone.g=g;//goal position, object in the map array
		clone.tileWidth=tileWidth;
		clone.tileHeight=tileHeight;
		return clone;
	}
	//////////////////////////////////////////////////////////////////
	public function addStair(row:Number, column:Number):void
	{
		stairs.push(map[row][column]);
		map[row][column].isStair = true;
	}
	
	public function getTile(column, row):Object 
	{
		return map[column][row];
	}
	
	public function removeStair(row:Number, column:Number):void
	{
		for (var i = 0; i<stairs.length; ++i) {
			if (stairs[i].x == row && stairs[i].y == column) {
				stairs[i].isStair = false;
				stairs.splice(i, 1);
				break;
			}
		}
	}
	public function initialize():void 
	{
		//This method is called after terrains have been defined and setBase() has been called
		//This method creates a 2D array representing the map. Each element in it is an object.
		//The tile object contains information regarding its terrain, if it is solid or not, and the objects (like rocks, trees, tools) on that tile
		for (var i = 0; i<columns; ++i) {
			map[i] = new Array();
			for (var j = 0; j<rows; ++j) {
				emptyTile(i, j);
			}
		}
	}
	public function addTerrain(tmp_terrain:Array):void 
	{
		//This just creates a bunch of terrain objects from the strings in the array passed in
		//The 'cost' of a terrain is only defined when moving from one tile to the next. See 'setTransitionCost'
		for (var i = 0; i<tmp_terrain.length; ++i) {
			terrain[tmp_terrain[i]] = {type:tmp_terrain[i]};
		
		}
	}
	public function setBase(newTerrain:String):void 
	{
		//This method is used to set a default terrain type, such as 'grass'. Then all newly created tiles will use that terrain
		terrain.base = newTerrain;
		
	}
	public function setTransitionCost(terrain1:String, terrain2:String, cost:Number, reversible:Boolean = false):void
	{
		//There is a cost associated from moving from one terrain to another. This method sets those values
		//Moving from concrete to grass should not be a large cost. However, moving from ground to water should be.
		//When these transition values are assigned well, A* will generate more realistic paths
		terrain[terrain1][terrain2] = cost;
		if (reversible) {
			terrain[terrain2][terrain1] = cost;
		}
	}
	public function modifyTileTerrain(row:Number, column:Number, tempTerrain:String):void
	{
		//This method is used to change the terrain associated with a tile
		var solid:Boolean = false;
		if (tempTerrain.toLowerCase() == terrain.block.toLowerCase() || containsSolidObject(map[row][column].objects)) {
			solid = true;
		}
		map[row][column].solid = solid;//edited..........t.w.
		map[row][column].terrain = tempTerrain;
	}
	public function addObject(row:Number, column:Number, type:String, walkable:Boolean=false, extra:Array=null):Number
	{
		//Adds an object to a tile
		//'type' is a string that describes the object, such as "tree"
		//'walkable' is a boolean. If 'false' then the object forces this tile not to let someone walk on it
		//The default for 'walkable' is false
		//'extra' is just any extra information to store about the object, such as which frame to display
		var tile:Object = map[row][column];
		if (tile.objects == undefined) {
			tile.objects = new Array();
			tile.ob_ids = 0;
		}
		++tile.ob_ids;
		var ob:Object = new Object();
		ob.type = type;
		if (extra) {
			ob.extra = extra;
		}
		if (!walkable) {
			var walkable:Boolean = false;
		}
		ob.walkable = walkable;
		ob.id = tile.ob_ids;
		tile.objects.push(ob);
		if (containsSolidObject(tile.objects)) {
			tile.solid = true;
		}
		return ob.id;
	}
	public function removeObject(row:Number, column:Number, id:Number):void
	{
		//When an object is created it is assigned an id. To remove that object this method is 
		//called and the id is passed in
		//If no id is passed in then all objects in this tile are removed
		var tile:Object = map[row][column];
		var objects:Array = tile.objects;
		if (id) {
			for (var i = 0; i<objects.length; ++i) {
				if (objects[i].id == id) {
					objects.splice(i, 1);
					break;
				}
			}
		} else {
			map[row][column].objects = undefined;
			objects = undefined;
		}
		if (tile.terrain.toLowerCase() == terrain.block.toLowerCase() || containsSolidObject(objects)) {
			tile.solid = true;
		} else {
			tile.solid = false;
		}
	}
	public function containsSolidObject(objects:Array):Boolean
	{
		//This method is used internally to determine if a tile contains any objects that are unwalkable
		//An unwalkable object is one that forbids the tile to become part of a path
		var containsSolid:Boolean = false;
		if(objects){
		for (var i:Number = 0; i<objects.length; ++i) {
			if (objects[i].walkable != true) {
				containsSolid = true;
				break;
			}
		}
		};
		return containsSolid;
	}
	public function emptyTile(row:Number, column:Number):void
	{
		//This resets a tile
		map[row][column] = new Object();
		map[row][column].x = row;
		map[row][column].y = column;
		modifyTileTerrain(row, column, terrain.base);
	}
	public function setStart(row:Number, column:Number):void
	{
		//This is the start position for the next path to be calculated
		s = map[row][column];
	}
	public function setGoal(row:Number, column:Number):void
	{
		//This is the destination position for the next path to be calculated
		g = map[row][column];
	}
	public function setBlock(name:String):void
	{
		//Associates a terrain type with being unwalkable, use this to make, say concrete walls unwalkable
		terrain.block = name;
	}
	public function xmlToMap(data:XML):void
	{
		//Takes a valid map XML document (created at some point with the toXML() method) and parses it
		//This method recreates the map data structure here
		var rootNodes:Array = data.firstChild.childNodes;
		for (var ino:Number = 0; ino<rootNodes.length; ++ino) {
			var rootNodeName:String = rootNodes[ino].nodeName;
			if (rootNodeName == "header") {
				rows = Number(rootNodes[ino].attributes.rows);
				columns = Number(rootNodes[ino].attributes.columns);
				tileWidth = Number(rootNodes[ino].attributes.tileWidth);
				tileHeight = Number(rootNodes[ino].attributes.tileHeight);
			} else if (rootNodeName == "terrains") {
				var rootTerrains = rootNodes[ino];
				setBase(rootTerrains.attributes.base);
				setBlock(rootTerrains.attributes.block);
				var terrainNodes:Array = rootTerrains.childNodes;
				for (var jno:Number = 0; jno<terrainNodes.length; ++jno) {
					var terrainName:String = terrainNodes[jno].attributes.name;
					terrain[terrainName] = new Object();
					var terrainOb:Object = terrain[terrainName];
					terrainOb.type = terrainName;
					var costs:Array = terrainNodes[jno].childNodes;
					for (var kno:Number = 0; kno<costs.length; ++kno) {
						var terrain2:String = costs[kno].attributes.name;
						var value:Number = Number(costs[kno].attributes.value);
						terrainOb[terrain2] = value;
					}
				}
			} else if (rootNodeName == "map") {
				initialize();
				var columns:Array = rootNodes[ino].childNodes;
				for (var j:Number = 0; j<columns.length; ++j) {
					var rows:Array = columns[j].childNodes;
					for (var k:Number = 0; k<rows.length; ++k) {
						var tile = rows[k];
						var tileTerrain:String = tile.attributes.terrain;
						modifyTileTerrain(j, k, tileTerrain);
						var tileSubNodes:Array = tile.childNodes;
						for (var l:Number = 0; l<tileSubNodes.length; ++l) {
							if (tileSubNodes[l].nodeName == "objects") {
								var tileObjects:Array = tileSubNodes[l].childNodes;
								for (var m:Number = 0; m<tileObjects.length; ++m) {
									var tileOb = tileObjects[m].attributes;
									addObject(j, k, tileOb.type, (tileOb.walkable == "true" ? true : false), tileOb.extra);
								}
							}
						}
					}
				}
			} else if (rootNodeName == "stairs") {
				var stairNodes:Array = rootNodes[ino].childNodes;
				for (jno = 0; jno<stairNodes.length; ++jno) {
					var row:Number = Number(stairNodes[jno].attributes.x);
					var column:Number = Number(stairNodes[jno].attributes.y);
					addStair(row, column);
				}
			}
		}
	}
	public function toXML():String
	{
		//Creates an XML representation of the map object
		var doc:String = "<doc><header rows=\""+rows+"\" columns=\""+columns+"\"/>";
		//Handle terrain
		doc += "<terrains base=\""+terrain.base+"\" block=\""+terrain.block+"\">";
		for (var ist:String in terrain) {
			if (ist != "block" && ist != "base") {
				doc += "<terrain name=\""+ist+"\">";
				for (var js:String in terrain[ist]) {
					if (js != "type") {
						doc += "<cost name=\""+js+"\" value=\""+terrain[ist][js]+"\"/>";
					}
				}
				doc += "</terrain>";
			}
		}
		doc += "</terrains>";
		//Handle the map
		doc += "<map>";
		for (var ino:Number = 0; ino<map.length; ++ino) {
			doc += "<column>";
			for (var jno:Number = 0; jno<map[ino].length; ++jno) {
				doc += "<row terrain=\""+map[ino][jno].terrain+"\"";
				if (map[ino][jno].isStair) {
					doc += " isStair=\"true\"";
				}
				if (map[ino][jno].objects.length>0) {
					doc += "><objects>";
					for (var kno:Number = 0; kno<map[ino][jno].objects.length; ++kno) {
						doc += "<object type=\""+map[ino][jno].objects[kno].type+"\" walkable=\""+map[ino][jno].objects[kno].walkable+"\" extra=\""+map[ino][jno].objects[kno].extra+"\"/>";
					}
					doc += "</objects></row>";
				} else {
					doc += "/>";
				}
			}
			doc += "</column>";
		}
		doc += "</map>";
		//Handle the stairs
		doc += "<stairs>";
		for (var i = 0; i<stairs.length; ++i) {
			doc += "<stair x=\""+stairs[i].x+" y=\""+stairs[i].y+"\"/>";
		}
		doc += "</stairs>";
		doc += "</doc>";
		return doc;
	}


	}
}